{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Save and load forecasters\n",
    "\n",
    "Skforecast models can be easily saved and loaded from disk using the joblib library. Two handy functions, `save_forecaster` and `load_forecaster` are available to streamline this process. See below for a simple example.\n",
    "\n",
    "A `forecaster_id` has been included when initializing the Forecaster, this may help to identify the target of the model."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"admonition note\" name=\"html-admonition\" style=\"background: rgba(0,184,212,.1); padding-top: 0px; padding-bottom: 6px; border-radius: 8px; border-left: 8px solid #00b8d4; border-color: #00b8d4; padding-left: 10px; padding-right: 10px;\">\n",
    "\n",
    "<p class=\"title\">\n",
    "    <i style=\"font-size: 18px; color:#00b8d4;\"></i>\n",
    "    <b style=\"color: #00b8d4;\">&#9998 Note</b>\n",
    "</p>\n",
    "\n",
    "Learn how to use <a href=\"../user_guides/forecaster-in-production.html\">forecaster models in production</a>.\n",
    "\n",
    "</div>"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Libraries and data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Libraries\n",
    "# ==============================================================================\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "from sklearn.ensemble import RandomForestRegressor\n",
    "from skforecast.datasets import fetch_dataset\n",
    "from skforecast.recursive import ForecasterRecursive\n",
    "from skforecast.recursive import ForecasterRecursiveMultiSeries\n",
    "from skforecast.utils import save_forecaster\n",
    "from skforecast.utils import load_forecaster"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">╭────────────────────────────────────── <span style=\"font-weight: bold\">h2o</span> ───────────────────────────────────────╮\n",
       "│ <span style=\"font-weight: bold\">Description:</span>                                                                     │\n",
       "│ Monthly expenditure ($AUD) on corticosteroid drugs that the Australian health    │\n",
       "│ system had between 1991 and 2008.                                                │\n",
       "│                                                                                  │\n",
       "│ <span style=\"font-weight: bold\">Source:</span>                                                                          │\n",
       "│ Hyndman R (2023). fpp3: Data for Forecasting: Principles and Practice(3rd        │\n",
       "│ Edition). http://pkg.robjhyndman.com/fpp3package/,https://github.com/robjhyndman │\n",
       "│ /fpp3package, http://OTexts.com/fpp3.                                            │\n",
       "│                                                                                  │\n",
       "│ <span style=\"font-weight: bold\">URL:</span>                                                                             │\n",
       "│ https://raw.githubusercontent.com/skforecast/skforecast-                         │\n",
       "│ datasets/main/data/h2o.csv                                                       │\n",
       "│                                                                                  │\n",
       "│ <span style=\"font-weight: bold\">Shape:</span> 204 rows x 2 columns                                                      │\n",
       "╰──────────────────────────────────────────────────────────────────────────────────╯\n",
       "</pre>\n"
      ],
      "text/plain": [
       "╭────────────────────────────────────── \u001b[1mh2o\u001b[0m ───────────────────────────────────────╮\n",
       "│ \u001b[1mDescription:\u001b[0m                                                                     │\n",
       "│ Monthly expenditure ($AUD) on corticosteroid drugs that the Australian health    │\n",
       "│ system had between 1991 and 2008.                                                │\n",
       "│                                                                                  │\n",
       "│ \u001b[1mSource:\u001b[0m                                                                          │\n",
       "│ Hyndman R (2023). fpp3: Data for Forecasting: Principles and Practice(3rd        │\n",
       "│ Edition). http://pkg.robjhyndman.com/fpp3package/,https://github.com/robjhyndman │\n",
       "│ /fpp3package, http://OTexts.com/fpp3.                                            │\n",
       "│                                                                                  │\n",
       "│ \u001b[1mURL:\u001b[0m                                                                             │\n",
       "│ https://raw.githubusercontent.com/skforecast/skforecast-                         │\n",
       "│ datasets/main/data/h2o.csv                                                       │\n",
       "│                                                                                  │\n",
       "│ \u001b[1mShape:\u001b[0m 204 rows x 2 columns                                                      │\n",
       "╰──────────────────────────────────────────────────────────────────────────────────╯\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Download data\n",
    "# ==============================================================================\n",
    "data = fetch_dataset(\n",
    "    name=\"h2o\", raw=True, kwargs_read_csv={\"names\": [\"y\", \"date\"], \"header\": 0}\n",
    ")\n",
    "data['date'] = pd.to_datetime(data['date'], format='%Y-%m-%d')\n",
    "data = data.set_index('date')\n",
    "data = data.asfreq('MS')"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Save and load forecaster model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2008-07-01    0.714526\n",
       "2008-08-01    0.789144\n",
       "2008-09-01    0.818433\n",
       "Freq: MS, Name: pred, dtype: float64"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Create and train forecaster\n",
    "# ==============================================================================\n",
    "forecaster = ForecasterRecursive(\n",
    "                 estimator     = RandomForestRegressor(random_state=123),\n",
    "                 lags          = 5,\n",
    "                 forecaster_id = \"forecaster_001\"\n",
    "             )\n",
    "\n",
    "forecaster.fit(y=data['y'])\n",
    "forecaster.predict(steps=3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Save model\n",
    "# ==============================================================================\n",
    "save_forecaster(forecaster, file_name='forecaster_001.joblib', verbose=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=================== \n",
      "ForecasterRecursive \n",
      "=================== \n",
      "Estimator: RandomForestRegressor \n",
      "Lags: [1 2 3 4 5] \n",
      "Window features: None \n",
      "Window size: 5 \n",
      "Series name: y \n",
      "Exogenous included: False \n",
      "Exogenous names: None \n",
      "Transformer for y: None \n",
      "Transformer for exog: None \n",
      "Weight function included: False \n",
      "Differentiation order: None \n",
      "Training range: [Timestamp('1991-07-01 00:00:00'), Timestamp('2008-06-01 00:00:00')] \n",
      "Training index type: DatetimeIndex \n",
      "Training index frequency: <MonthBegin> \n",
      "Estimator parameters: \n",
      "    {'bootstrap': True, 'ccp_alpha': 0.0, 'criterion': 'squared_error', 'max_depth':\n",
      "    None, 'max_features': 1.0, 'max_leaf_nodes': None, 'max_samples': None,\n",
      "    'min_impurity_decrease': 0.0, 'min_samples_leaf': 1, 'min_samples_split': 2,\n",
      "    'min_weight_fraction_leaf': 0.0, 'monotonic_cst': None, 'n_estimators': 100,\n",
      "    'n_jobs': None, 'oob_score': False, 'random_state': 123, 'verbose': 0,\n",
      "    'warm_start': False} \n",
      "fit_kwargs: {} \n",
      "Creation date: 2025-11-27 12:00:11 \n",
      "Last fit date: 2025-11-27 12:00:12 \n",
      "Skforecast version: 0.19.0 \n",
      "Python version: 3.12.11 \n",
      "Forecaster id: forecaster_001 \n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Load model\n",
    "# ==============================================================================\n",
    "forecaster_loaded = load_forecaster('forecaster_001.joblib', verbose=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2008-07-01    0.714526\n",
       "2008-08-01    0.789144\n",
       "2008-09-01    0.818433\n",
       "Freq: MS, Name: pred, dtype: float64"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Predict\n",
    "# ==============================================================================\n",
    "forecaster_loaded.predict(steps=3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'forecaster_001'"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Forecaster identifier\n",
    "# ==============================================================================\n",
    "forecaster.forecaster_id"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Saving and Loading a Forecaster Model with Custom Features\n",
    "\n",
    "Sometimes external objects are needed when creating a Forecaster. For example:\n",
    "\n",
    "+ Custom class to [create window and custom features](../user_guides/window-features-and-custom-features.html#create-your-custom-window-features).\n",
    "\n",
    "+ A function to reduce the impact of some dates on the model, [Weighted Time Series Forecasting](../user_guides/weighted-time-series-forecasting.html). \n",
    "\n",
    "For your code to work properly, these functions must be available in the environment where the Forecaster is loaded."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Custom class to create rolling skewness features\n",
    "# ==============================================================================\n",
    "from scipy.stats import skew\n",
    "\n",
    "\n",
    "class RollingSkewness():\n",
    "    \"\"\"\n",
    "    Custom class to create rolling skewness features.\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, window_sizes, features_names: list = 'rolling_skewness'):\n",
    "        \n",
    "        if not isinstance(window_sizes, list):\n",
    "            window_sizes = [window_sizes]\n",
    "        self.window_sizes = window_sizes\n",
    "        self.features_names = features_names\n",
    "\n",
    "    def transform_batch(self, X: pd.Series) -> pd.DataFrame:\n",
    "        \n",
    "        rolling_obj = X.rolling(window=self.window_sizes[0], center=False, closed='left')\n",
    "        rolling_skewness = rolling_obj.skew()\n",
    "        rolling_skewness = pd.DataFrame({\n",
    "                               self.features_names: rolling_skewness\n",
    "                           }).dropna()\n",
    "\n",
    "        return rolling_skewness\n",
    "\n",
    "    def transform(self, X: np.ndarray) -> np.ndarray:\n",
    "        \n",
    "        X = X[~np.isnan(X)]\n",
    "        if len(X) > 0:\n",
    "            rolling_skewness = np.array([skew(X, bias=False)])\n",
    "        else:\n",
    "            rolling_skewness = np.array([np.nan])\n",
    "        \n",
    "        return rolling_skewness"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Custom function to create weights\n",
    "# ==============================================================================\n",
    "def custom_weights(index):\n",
    "    \"\"\"\n",
    "    Return 0 if index is between 2004-01-01 and 2005-01-01.\n",
    "    \"\"\"\n",
    "    weights = np.where(\n",
    "                  (index >= '2004-01-01') & (index <= '2005-01-01'),\n",
    "                   0,\n",
    "                   1\n",
    "              )\n",
    "\n",
    "    return weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create and train forecaster\n",
    "# ==============================================================================\n",
    "window_features = RollingSkewness(window_sizes=3)\n",
    "\n",
    "forecaster = ForecasterRecursive(\n",
    "                 estimator       = RandomForestRegressor(random_state=123),\n",
    "                 lags            = 3,\n",
    "                 window_features = window_features,\n",
    "                 weight_func     = custom_weights,\n",
    "                 forecaster_id   = \"forecaster_custom_features\"\n",
    "             )\n",
    "\n",
    "forecaster.fit(y=data['y'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"admonition note\" name=\"html-admonition\" style=\"background: rgba(255,145,0,.1); padding-top: 0px; padding-bottom: 6px; border-radius: 8px; border-left: 8px solid #ff9100; border-color: #ff9100; padding-left: 10px; padding-right: 10px\">\n",
    "\n",
    "<p class=\"title\">\n",
    "    <i style=\"font-size: 18px; color:#ff9100; border-color: #ff1744;\"></i>\n",
    "    <b style=\"color: #ff9100;\"> <span style=\"color: #ff9100;\">&#9888;</span> Warning</b>\n",
    "</p>\n",
    "\n",
    "The <code>save_forecaster</code> function will save the functions used to create the weights as a module (<code>custom_weights.py</code>). <b>But the classes used to create the window features will not be saved</b>. Therefore, you must ensure that these classes are available in the environment where the Forecaster is loaded.\n",
    "\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">╭───────────────────────────── SaveLoadSkforecastWarning ──────────────────────────────╮</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> The Forecaster includes custom user-defined classes in the `window_features`         <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> argument. These classes are not saved automatically when saving the Forecaster.      <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> Please ensure you save these classes manually and import them before loading the     <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> Forecaster.                                                                          <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>     Custom classes: RollingSkewness                                                  <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> Visit the documentation for more information:                                        <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> https://skforecast.org/latest/user_guides/save-load-forecaster.html#saving-and-loadi <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> ng-a-forecaster-model-with-custom-features                                           <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>                                                                                      <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> Category : skforecast.exceptions.SaveLoadSkforecastWarning                           <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> Location :                                                                           <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> c:\\Users\\jaesc2\\Miniconda3\\envs\\skforecast_py12\\Lib\\site-packages\\skforecast\\utils\\u <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> tils.py:1997                                                                         <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> Suppress : warnings.simplefilter('ignore', category=SaveLoadSkforecastWarning)       <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">╰──────────────────────────────────────────────────────────────────────────────────────╯</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[38;5;214m╭─\u001b[0m\u001b[38;5;214m────────────────────────────\u001b[0m\u001b[38;5;214m SaveLoadSkforecastWarning \u001b[0m\u001b[38;5;214m─────────────────────────────\u001b[0m\u001b[38;5;214m─╮\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m The Forecaster includes custom user-defined classes in the `window_features`         \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m argument. These classes are not saved automatically when saving the Forecaster.      \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m Please ensure you save these classes manually and import them before loading the     \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m Forecaster.                                                                          \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m     Custom classes: RollingSkewness                                                  \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m Visit the documentation for more information:                                        \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m https://skforecast.org/latest/user_guides/save-load-forecaster.html#saving-and-loadi \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m ng-a-forecaster-model-with-custom-features                                           \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m                                                                                      \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m Category : skforecast.exceptions.SaveLoadSkforecastWarning                           \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m Location :                                                                           \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m c:\\Users\\jaesc2\\Miniconda3\\envs\\skforecast_py12\\Lib\\site-packages\\skforecast\\utils\\u \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m tils.py:1997                                                                         \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m Suppress : warnings.simplefilter('ignore', category=SaveLoadSkforecastWarning)       \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m╰──────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Save model and custom function\n",
    "# ==============================================================================\n",
    "save_forecaster(\n",
    "    forecaster, \n",
    "    file_name = 'forecaster_custom_features.joblib', \n",
    "    save_custom_functions = True, \n",
    "    verbose = False\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At this point, the `RollingSkewness` class is manually saved in a file called `rolling_skewness.py`. This file must be available in the environment where the Forecaster is loaded."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=================== \n",
      "ForecasterRecursive \n",
      "=================== \n",
      "Estimator: RandomForestRegressor \n",
      "Lags: [1 2 3] \n",
      "Window features: ['rolling_skewness'] \n",
      "Window size: 3 \n",
      "Series name: y \n",
      "Exogenous included: False \n",
      "Exogenous names: None \n",
      "Transformer for y: None \n",
      "Transformer for exog: None \n",
      "Weight function included: True \n",
      "Differentiation order: None \n",
      "Training range: [Timestamp('1991-07-01 00:00:00'), Timestamp('2008-06-01 00:00:00')] \n",
      "Training index type: DatetimeIndex \n",
      "Training index frequency: <MonthBegin> \n",
      "Estimator parameters: \n",
      "    {'bootstrap': True, 'ccp_alpha': 0.0, 'criterion': 'squared_error', 'max_depth':\n",
      "    None, 'max_features': 1.0, 'max_leaf_nodes': None, 'max_samples': None,\n",
      "    'min_impurity_decrease': 0.0, 'min_samples_leaf': 1, 'min_samples_split': 2,\n",
      "    'min_weight_fraction_leaf': 0.0, 'monotonic_cst': None, 'n_estimators': 100,\n",
      "    'n_jobs': None, 'oob_score': False, 'random_state': 123, 'verbose': 0,\n",
      "    'warm_start': False} \n",
      "fit_kwargs: {} \n",
      "Creation date: 2025-11-27 12:00:12 \n",
      "Last fit date: 2025-11-27 12:00:12 \n",
      "Skforecast version: 0.19.0 \n",
      "Python version: 3.12.11 \n",
      "Forecaster id: forecaster_custom_features \n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Load model and custom function\n",
    "# ==============================================================================\n",
    "from rolling_skewness import RollingSkewness  # This file has to be generated manually\n",
    "from custom_weights import custom_weights\n",
    "\n",
    "forecaster_loaded = load_forecaster('forecaster_custom_features.joblib', verbose=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2008-07-01    0.808125\n",
       "2008-08-01    0.859447\n",
       "2008-09-01    0.933751\n",
       "2008-10-01    0.950768\n",
       "2008-11-01    0.914137\n",
       "Freq: MS, Name: pred, dtype: float64"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Predict using loaded forecaster\n",
    "# ==============================================================================\n",
    "forecaster_loaded.predict(steps=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### ForecasterRecursiveMultiSeries"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When using a <code>ForecasterRecursiveMultiSeries</code>, the <code>save_forecaster</code> function will save a different module for each of the functions used to create the weights."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\">╭─────────────────────── <span style=\"font-weight: bold\">items_sales</span> ───────────────────────╮\n",
       "│ <span style=\"font-weight: bold\">Description:</span>                                              │\n",
       "│ Simulated time series for the sales of 3 different items. │\n",
       "│                                                           │\n",
       "│ <span style=\"font-weight: bold\">Source:</span>                                                   │\n",
       "│ Simulated data.                                           │\n",
       "│                                                           │\n",
       "│ <span style=\"font-weight: bold\">URL:</span>                                                      │\n",
       "│ https://raw.githubusercontent.com/skforecast/skforecast-  │\n",
       "│ datasets/main/data/simulated_items_sales.csv              │\n",
       "│                                                           │\n",
       "│ <span style=\"font-weight: bold\">Shape:</span> 1097 rows x 3 columns                              │\n",
       "╰───────────────────────────────────────────────────────────╯\n",
       "</pre>\n"
      ],
      "text/plain": [
       "╭─────────────────────── \u001b[1mitems_sales\u001b[0m ───────────────────────╮\n",
       "│ \u001b[1mDescription:\u001b[0m                                              │\n",
       "│ Simulated time series for the sales of 3 different items. │\n",
       "│                                                           │\n",
       "│ \u001b[1mSource:\u001b[0m                                                   │\n",
       "│ Simulated data.                                           │\n",
       "│                                                           │\n",
       "│ \u001b[1mURL:\u001b[0m                                                      │\n",
       "│ https://raw.githubusercontent.com/skforecast/skforecast-  │\n",
       "│ datasets/main/data/simulated_items_sales.csv              │\n",
       "│                                                           │\n",
       "│ \u001b[1mShape:\u001b[0m 1097 rows x 3 columns                              │\n",
       "╰───────────────────────────────────────────────────────────╯\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>item_1</th>\n",
       "      <th>item_2</th>\n",
       "      <th>item_3</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>date</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>2012-01-01</th>\n",
       "      <td>8.253175</td>\n",
       "      <td>21.047727</td>\n",
       "      <td>19.429739</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2012-01-02</th>\n",
       "      <td>22.777826</td>\n",
       "      <td>26.578125</td>\n",
       "      <td>28.009863</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2012-01-03</th>\n",
       "      <td>27.549099</td>\n",
       "      <td>31.751042</td>\n",
       "      <td>32.078922</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2012-01-04</th>\n",
       "      <td>25.895533</td>\n",
       "      <td>24.567708</td>\n",
       "      <td>27.252276</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2012-01-05</th>\n",
       "      <td>21.379238</td>\n",
       "      <td>18.191667</td>\n",
       "      <td>20.357737</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "               item_1     item_2     item_3\n",
       "date                                       \n",
       "2012-01-01   8.253175  21.047727  19.429739\n",
       "2012-01-02  22.777826  26.578125  28.009863\n",
       "2012-01-03  27.549099  31.751042  32.078922\n",
       "2012-01-04  25.895533  24.567708  27.252276\n",
       "2012-01-05  21.379238  18.191667  20.357737"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Data download\n",
    "# ==============================================================================\n",
    "data = fetch_dataset(name=\"items_sales\")\n",
    "data.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Custom function to create weights for each item\n",
    "# ==============================================================================\n",
    "def custom_weights_item_1(index):\n",
    "    \"\"\"\n",
    "    Return 0 if index is between 2012-01-01 and 2012-06-01.\n",
    "    \"\"\"\n",
    "    weights = np.where(\n",
    "        (index >= '2012-01-01') & (index <= '2012-06-01'), 0, 1\n",
    "    )\n",
    "\n",
    "    return weights\n",
    "\n",
    "def custom_weights_item_2(index):\n",
    "    \"\"\"\n",
    "    Return 0 if index is between 2012-04-01 and 2013-01-01.\n",
    "    \"\"\"\n",
    "    weights = np.where(\n",
    "        (index >= '2012-04-01') & (index <= '2013-01-01'), 0, 1\n",
    "    )\n",
    "\n",
    "    return weights\n",
    "\n",
    "def custom_weights_item_3(index):\n",
    "    \"\"\"\n",
    "    Return 0 if index is between 2012-06-01 and 2013-01-01.\n",
    "    \"\"\"\n",
    "    weights = np.where(\n",
    "        (index >= '2012-06-01') & (index <= '2013-01-01'), 0, 1\n",
    "    )\n",
    "\n",
    "    return weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Custom class to create rolling skewness features (multi-series)\n",
    "# ==============================================================================\n",
    "from scipy.stats import skew\n",
    "\n",
    "\n",
    "class RollingSkewnessMultiSeries():\n",
    "    \"\"\"\n",
    "    Custom class to create rolling skewness features for multiple series.\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, window_sizes, features_names: list = 'rolling_skewness'):\n",
    "        \n",
    "        if not isinstance(window_sizes, list):\n",
    "            window_sizes = [window_sizes]\n",
    "        self.window_sizes = window_sizes\n",
    "        self.features_names = features_names\n",
    "\n",
    "    def transform_batch(self, X: pd.Series) -> pd.DataFrame:\n",
    "        \n",
    "        rolling_obj = X.rolling(window=self.window_sizes[0], center=False, closed='left')\n",
    "        rolling_skewness = rolling_obj.skew()\n",
    "        rolling_skewness = pd.DataFrame({\n",
    "                               self.features_names: rolling_skewness\n",
    "                           }).dropna()\n",
    "\n",
    "        return rolling_skewness\n",
    "\n",
    "    def transform(self, X: np.ndarray) -> np.ndarray:\n",
    "        \n",
    "        X_dim = X.ndim\n",
    "        if X_dim == 1:\n",
    "            n_series = 1  # Only one series\n",
    "            X = X.reshape(-1, 1)\n",
    "        else:\n",
    "            n_series = X.shape[1]  # Series (levels) to be predicted (present in last_window)\n",
    "        \n",
    "        n_stats = 1  # Only skewness is calculated\n",
    "        rolling_skewness = np.full(\n",
    "            shape=(n_series, n_stats), fill_value=np.nan, dtype=float\n",
    "        )\n",
    "        for i in range(n_series):\n",
    "            if len(X) > 0:\n",
    "                rolling_skewness[i, :] = skew(X[:, i], bias=False)\n",
    "            else:\n",
    "                rolling_skewness[i, :] = np.nan      \n",
    "\n",
    "        if X_dim == 1:\n",
    "            rolling_skewness = rolling_skewness.flatten()  \n",
    "        \n",
    "        return rolling_skewness"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"admonition note\" name=\"html-admonition\" style=\"background: rgba(255,145,0,.1); padding-top: 0px; padding-bottom: 6px; border-radius: 8px; border-left: 8px solid #ff9100; border-color: #ff9100; padding-left: 10px; padding-right: 10px\">\n",
    "\n",
    "<p class=\"title\">\n",
    "    <i style=\"font-size: 18px; color:#ff9100; border-color: #ff1744;\"></i>\n",
    "    <b style=\"color: #ff9100;\"> <span style=\"color: #ff9100;\">&#9888;</span> Warning</b>\n",
    "</p>\n",
    "\n",
    "When <code>weight_func</code> is a <code>dict</code> and does not contain any of the series, for instance: \n",
    "\n",
    "```python\n",
    "# Weights are not included for item_2\n",
    "weight_func_dict = {\n",
    "    'item_1': custom_weights_item_1,\n",
    "    'item_3': custom_weights_item_3\n",
    "}\n",
    "```\n",
    "\n",
    "You must create a function that returns all 1's as weights of that series.\n",
    "\n",
    "```python\n",
    "def custom_weights_all_1(index):\n",
    "    \"\"\"\n",
    "    Return 1 for all elements in the index.\n",
    "    \"\"\"\n",
    "    weights = np.ones(len(index))\n",
    "    return weights\n",
    "\n",
    "# item_2 dummy weights\n",
    "weight_func_dict = {\n",
    "    'item_1': custom_weights_item_1,\n",
    "    'item_2': custom_weights_all_1,\n",
    "    'item_3': custom_weights_item_3\n",
    "}\n",
    "```\n",
    "\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">╭────────────────────────────────── InputTypeWarning ──────────────────────────────────╮</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> Passing a DataFrame (either wide or long format) as `series` requires additional     <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> internal transformations, which can increase computational time. It is recommended   <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> to use a dictionary of pandas Series instead. For more details, see:                 <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> https://skforecast.org/latest/user_guides/independent-multi-time-series-forecasting. <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> html#input-data                                                                      <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>                                                                                      <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> Category : skforecast.exceptions.InputTypeWarning                                    <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> Location :                                                                           <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> c:\\Users\\jaesc2\\Miniconda3\\envs\\skforecast_py12\\Lib\\site-packages\\skforecast\\utils\\u <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> tils.py:2349                                                                         <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> Suppress : warnings.simplefilter('ignore', category=InputTypeWarning)                <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">╰──────────────────────────────────────────────────────────────────────────────────────╯</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[38;5;214m╭─\u001b[0m\u001b[38;5;214m─────────────────────────────────\u001b[0m\u001b[38;5;214m InputTypeWarning \u001b[0m\u001b[38;5;214m─────────────────────────────────\u001b[0m\u001b[38;5;214m─╮\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m Passing a DataFrame (either wide or long format) as `series` requires additional     \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m internal transformations, which can increase computational time. It is recommended   \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m to use a dictionary of pandas Series instead. For more details, see:                 \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m https://skforecast.org/latest/user_guides/independent-multi-time-series-forecasting. \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m html#input-data                                                                      \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m                                                                                      \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m Category : skforecast.exceptions.InputTypeWarning                                    \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m Location :                                                                           \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m c:\\Users\\jaesc2\\Miniconda3\\envs\\skforecast_py12\\Lib\\site-packages\\skforecast\\utils\\u \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m tils.py:2349                                                                         \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m Suppress : warnings.simplefilter('ignore', category=InputTypeWarning)                \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m╰──────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "\n",
       "    <style>\n",
       "        .container-563afa5fcd3741f2855b5468bf3585a5 {\n",
       "            font-family: 'Arial', sans-serif;\n",
       "            font-size: 0.9em;\n",
       "            color: #333333;\n",
       "            border: 1px solid #ddd;\n",
       "            background-color: #f0f8ff;\n",
       "            padding: 5px 15px;\n",
       "            border-radius: 8px;\n",
       "            max-width: 600px;\n",
       "            #margin: auto;\n",
       "        }\n",
       "        .container-563afa5fcd3741f2855b5468bf3585a5 h2 {\n",
       "            font-size: 1.5em;\n",
       "            color: #222222;\n",
       "            border-bottom: 2px solid #ddd;\n",
       "            padding-bottom: 5px;\n",
       "            margin-bottom: 15px;\n",
       "            margin-top: 5px;\n",
       "        }\n",
       "        .container-563afa5fcd3741f2855b5468bf3585a5 details {\n",
       "            margin: 10px 0;\n",
       "        }\n",
       "        .container-563afa5fcd3741f2855b5468bf3585a5 summary {\n",
       "            font-weight: bold;\n",
       "            font-size: 1.1em;\n",
       "            color: #000000;\n",
       "            cursor: pointer;\n",
       "            margin-bottom: 5px;\n",
       "            background-color: #b3dbfd;\n",
       "            padding: 5px;\n",
       "            border-radius: 5px;\n",
       "        }\n",
       "        .container-563afa5fcd3741f2855b5468bf3585a5 summary:hover {\n",
       "            color: #000000;\n",
       "            background-color: #e0e0e0;\n",
       "        }\n",
       "        .container-563afa5fcd3741f2855b5468bf3585a5 ul {\n",
       "            font-family: 'Courier New', monospace;\n",
       "            list-style-type: none;\n",
       "            padding-left: 20px;\n",
       "            margin: 10px 0;\n",
       "            line-height: normal;\n",
       "        }\n",
       "        .container-563afa5fcd3741f2855b5468bf3585a5 li {\n",
       "            margin: 5px 0;\n",
       "            font-family: 'Courier New', monospace;\n",
       "        }\n",
       "        .container-563afa5fcd3741f2855b5468bf3585a5 li strong {\n",
       "            font-weight: bold;\n",
       "            color: #444444;\n",
       "        }\n",
       "        .container-563afa5fcd3741f2855b5468bf3585a5 li::before {\n",
       "            content: \"- \";\n",
       "            color: #666666;\n",
       "        }\n",
       "        .container-563afa5fcd3741f2855b5468bf3585a5 a {\n",
       "            color: #001633;\n",
       "            text-decoration: none;\n",
       "        }\n",
       "        .container-563afa5fcd3741f2855b5468bf3585a5 a:hover {\n",
       "            color: #359ccb; \n",
       "        }\n",
       "    </style>\n",
       "    \n",
       "        <div class=\"container-563afa5fcd3741f2855b5468bf3585a5\">\n",
       "            <p style=\"font-size: 1.5em; font-weight: bold; margin-block-start: 0.83em; margin-block-end: 0.83em;\">ForecasterRecursiveMultiSeries</p>\n",
       "            <details open>\n",
       "                <summary>General Information</summary>\n",
       "                <ul>\n",
       "                    <li><strong>Estimator:</strong> RandomForestRegressor</li>\n",
       "                    <li><strong>Lags:</strong> [1 2 3]</li>\n",
       "                    <li><strong>Window features:</strong> ['rolling_skewness']</li>\n",
       "                    <li><strong>Window size:</strong> 3</li>\n",
       "                    <li><strong>Series encoding:</strong> ordinal</li>\n",
       "                    <li><strong>Exogenous included:</strong> False</li>\n",
       "                    <li><strong>Weight function included:</strong> True</li>\n",
       "                    <li><strong>Series weights:</strong> None</li>\n",
       "                    <li><strong>Differentiation order:</strong> None</li>\n",
       "                    <li><strong>Creation date:</strong> 2025-11-27 12:00:12</li>\n",
       "                    <li><strong>Last fit date:</strong> 2025-11-27 12:00:13</li>\n",
       "                    <li><strong>Skforecast version:</strong> 0.19.0</li>\n",
       "                    <li><strong>Python version:</strong> 3.12.11</li>\n",
       "                    <li><strong>Forecaster id:</strong> None</li>\n",
       "                </ul>\n",
       "            </details>\n",
       "            <details>\n",
       "                <summary>Exogenous Variables</summary>\n",
       "                <ul>\n",
       "                    None\n",
       "                </ul>\n",
       "            </details>\n",
       "            <details>\n",
       "                <summary>Data Transformations</summary>\n",
       "                <ul>\n",
       "                    <li><strong>Transformer for series:</strong> None</li>\n",
       "                    <li><strong>Transformer for exog:</strong> None</li>\n",
       "                </ul>\n",
       "            </details>\n",
       "            <details>\n",
       "                <summary>Training Information</summary>\n",
       "                <ul>\n",
       "                    <li><strong>Series names (levels):</strong> item_1, item_2, item_3</li>\n",
       "                    <li><strong>Training range:</strong> 'item_1': ['2012-01-01', '2015-01-01'], 'item_2': ['2012-01-01', '2015-01-01'], 'item_3': ['2012-01-01', '2015-01-01']</li>\n",
       "                    <li><strong>Training index type:</strong> DatetimeIndex</li>\n",
       "                    <li><strong>Training index frequency:</strong> <Day></li>\n",
       "                </ul>\n",
       "            </details>\n",
       "            <details>\n",
       "                <summary>Estimator Parameters</summary>\n",
       "                <ul>\n",
       "                    {'bootstrap': True, 'ccp_alpha': 0.0, 'criterion': 'squared_error', 'max_depth': None, 'max_features': 1.0, 'max_leaf_nodes': None, 'max_samples': None, 'min_impurity_decrease': 0.0, 'min_samples_leaf': 1, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'monotonic_cst': None, 'n_estimators': 100, 'n_jobs': None, 'oob_score': False, 'random_state': 123, 'verbose': 0, 'warm_start': False}\n",
       "                </ul>\n",
       "            </details>\n",
       "            <details>\n",
       "                <summary>Fit Kwargs</summary>\n",
       "                <ul>\n",
       "                    {}\n",
       "                </ul>\n",
       "            </details>\n",
       "            <p>\n",
       "                <a href=\"https://skforecast.org/0.19.0/api/forecasterrecursivemultiseries.html\">&#128712 <strong>API Reference</strong></a>\n",
       "                &nbsp;&nbsp;\n",
       "                <a href=\"https://skforecast.org/0.19.0/user_guides/independent-multi-time-series-forecasting.html\">&#128462 <strong>User Guide</strong></a>\n",
       "            </p>\n",
       "        </div>\n",
       "        "
      ],
      "text/plain": [
       "============================== \n",
       "ForecasterRecursiveMultiSeries \n",
       "============================== \n",
       "Estimator: RandomForestRegressor \n",
       "Lags: [1 2 3] \n",
       "Window features: ['rolling_skewness'] \n",
       "Window size: 3 \n",
       "Series encoding: ordinal \n",
       "Series names (levels): item_1, item_2, item_3 \n",
       "Exogenous included: False \n",
       "Exogenous names: None \n",
       "Transformer for series: None \n",
       "Transformer for exog: None \n",
       "Weight function included: True \n",
       "Series weights: None \n",
       "Differentiation order: None \n",
       "Training range: \n",
       "    'item_1': ['2012-01-01', '2015-01-01'], 'item_2': ['2012-01-01', '2015-01-01'],\n",
       "    'item_3': ['2012-01-01', '2015-01-01'] \n",
       "Training index type: DatetimeIndex \n",
       "Training index frequency: <Day> \n",
       "Estimator parameters: \n",
       "    {'bootstrap': True, 'ccp_alpha': 0.0, 'criterion': 'squared_error', 'max_depth':\n",
       "    None, 'max_features': 1.0, 'max_leaf_nodes': None, 'max_samples': None,\n",
       "    'min_impurity_decrease': 0.0, 'min_samples_leaf': 1, 'min_samples_split': 2,\n",
       "    'min_weight_fraction_leaf': 0.0, 'monotonic_cst': None, 'n_estimators': 100,\n",
       "    'n_jobs': None, 'oob_score': False, 'random_state': 123, 'verbose': 0,\n",
       "    'warm_start': False} \n",
       "fit_kwargs: {} \n",
       "Creation date: 2025-11-27 12:00:12 \n",
       "Last fit date: 2025-11-27 12:00:13 \n",
       "Skforecast version: 0.19.0 \n",
       "Python version: 3.12.11 \n",
       "Forecaster id: None "
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Create and train ForecasterRecursiveMultiSeries\n",
    "# ==============================================================================\n",
    "window_features = RollingSkewnessMultiSeries(window_sizes=3)\n",
    "weight_func_dict = {\n",
    "    'item_1': custom_weights_item_1,\n",
    "    'item_2': custom_weights_item_2,\n",
    "    'item_3': custom_weights_item_3\n",
    "}\n",
    "\n",
    "forecaster = ForecasterRecursiveMultiSeries(\n",
    "                 estimator       = RandomForestRegressor(random_state=123),\n",
    "                 lags            = 3,\n",
    "                 window_features = window_features,\n",
    "                 encoding        = 'ordinal',\n",
    "                 weight_func     = weight_func_dict\n",
    "             )\n",
    "\n",
    "forecaster.fit(series=data)\n",
    "forecaster"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<pre style=\"white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace\"><span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">╭───────────────────────────── SaveLoadSkforecastWarning ──────────────────────────────╮</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> The Forecaster includes custom user-defined classes in the `window_features`         <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> argument. These classes are not saved automatically when saving the Forecaster.      <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> Please ensure you save these classes manually and import them before loading the     <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> Forecaster.                                                                          <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>     Custom classes: RollingSkewnessMultiSeries                                       <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> Visit the documentation for more information:                                        <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> https://skforecast.org/latest/user_guides/save-load-forecaster.html#saving-and-loadi <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> ng-a-forecaster-model-with-custom-features                                           <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>                                                                                      <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> Category : skforecast.exceptions.SaveLoadSkforecastWarning                           <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> Location :                                                                           <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> c:\\Users\\jaesc2\\Miniconda3\\envs\\skforecast_py12\\Lib\\site-packages\\skforecast\\utils\\u <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> tils.py:1997                                                                         <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span> Suppress : warnings.simplefilter('ignore', category=SaveLoadSkforecastWarning)       <span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">│</span>\n",
       "<span style=\"color: #ffaf00; text-decoration-color: #ffaf00\">╰──────────────────────────────────────────────────────────────────────────────────────╯</span>\n",
       "</pre>\n"
      ],
      "text/plain": [
       "\u001b[38;5;214m╭─\u001b[0m\u001b[38;5;214m────────────────────────────\u001b[0m\u001b[38;5;214m SaveLoadSkforecastWarning \u001b[0m\u001b[38;5;214m─────────────────────────────\u001b[0m\u001b[38;5;214m─╮\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m The Forecaster includes custom user-defined classes in the `window_features`         \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m argument. These classes are not saved automatically when saving the Forecaster.      \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m Please ensure you save these classes manually and import them before loading the     \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m Forecaster.                                                                          \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m     Custom classes: RollingSkewnessMultiSeries                                       \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m Visit the documentation for more information:                                        \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m https://skforecast.org/latest/user_guides/save-load-forecaster.html#saving-and-loadi \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m ng-a-forecaster-model-with-custom-features                                           \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m                                                                                      \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m Category : skforecast.exceptions.SaveLoadSkforecastWarning                           \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m Location :                                                                           \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m c:\\Users\\jaesc2\\Miniconda3\\envs\\skforecast_py12\\Lib\\site-packages\\skforecast\\utils\\u \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m tils.py:1997                                                                         \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m│\u001b[0m Suppress : warnings.simplefilter('ignore', category=SaveLoadSkforecastWarning)       \u001b[38;5;214m│\u001b[0m\n",
       "\u001b[38;5;214m╰──────────────────────────────────────────────────────────────────────────────────────╯\u001b[0m\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Save model and custom function\n",
    "# ==============================================================================\n",
    "save_forecaster(\n",
    "    forecaster, \n",
    "    file_name = 'forecaster_multiseries_custom_features.joblib', \n",
    "    save_custom_functions = True, \n",
    "    verbose = False\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "============================== \n",
      "ForecasterRecursiveMultiSeries \n",
      "============================== \n",
      "Estimator: RandomForestRegressor \n",
      "Lags: [1 2 3] \n",
      "Window features: ['rolling_skewness'] \n",
      "Window size: 3 \n",
      "Series encoding: ordinal \n",
      "Series names (levels): item_1, item_2, item_3 \n",
      "Exogenous included: False \n",
      "Exogenous names: None \n",
      "Transformer for series: None \n",
      "Transformer for exog: None \n",
      "Weight function included: True \n",
      "Series weights: None \n",
      "Differentiation order: None \n",
      "Training range: \n",
      "    'item_1': ['2012-01-01', '2015-01-01'], 'item_2': ['2012-01-01', '2015-01-01'],\n",
      "    'item_3': ['2012-01-01', '2015-01-01'] \n",
      "Training index type: DatetimeIndex \n",
      "Training index frequency: <Day> \n",
      "Estimator parameters: \n",
      "    {'bootstrap': True, 'ccp_alpha': 0.0, 'criterion': 'squared_error', 'max_depth':\n",
      "    None, 'max_features': 1.0, 'max_leaf_nodes': None, 'max_samples': None,\n",
      "    'min_impurity_decrease': 0.0, 'min_samples_leaf': 1, 'min_samples_split': 2,\n",
      "    'min_weight_fraction_leaf': 0.0, 'monotonic_cst': None, 'n_estimators': 100,\n",
      "    'n_jobs': None, 'oob_score': False, 'random_state': 123, 'verbose': 0,\n",
      "    'warm_start': False} \n",
      "fit_kwargs: {} \n",
      "Creation date: 2025-11-27 12:00:12 \n",
      "Last fit date: 2025-11-27 12:00:13 \n",
      "Skforecast version: 0.19.0 \n",
      "Python version: 3.12.11 \n",
      "Forecaster id: None \n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Load model and custom function\n",
    "# ==============================================================================\n",
    "from rolling_skewness import RollingSkewnessMultiSeries  # This file has to be generated manually\n",
    "from custom_weights_item_1 import custom_weights_item_1\n",
    "from custom_weights_item_2 import custom_weights_item_2\n",
    "from custom_weights_item_3 import custom_weights_item_3\n",
    "\n",
    "forecaster_loaded = load_forecaster(\n",
    "    'forecaster_multiseries_custom_features.joblib', verbose=True\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>level</th>\n",
       "      <th>pred</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>2015-01-02</th>\n",
       "      <td>item_1</td>\n",
       "      <td>14.818313</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2015-01-02</th>\n",
       "      <td>item_2</td>\n",
       "      <td>17.954045</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2015-01-02</th>\n",
       "      <td>item_3</td>\n",
       "      <td>19.676498</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2015-01-03</th>\n",
       "      <td>item_1</td>\n",
       "      <td>14.961743</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2015-01-03</th>\n",
       "      <td>item_2</td>\n",
       "      <td>17.530592</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2015-01-03</th>\n",
       "      <td>item_3</td>\n",
       "      <td>19.207165</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2015-01-04</th>\n",
       "      <td>item_1</td>\n",
       "      <td>18.349711</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2015-01-04</th>\n",
       "      <td>item_2</td>\n",
       "      <td>17.792810</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2015-01-04</th>\n",
       "      <td>item_3</td>\n",
       "      <td>19.919855</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2015-01-05</th>\n",
       "      <td>item_1</td>\n",
       "      <td>18.639790</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2015-01-05</th>\n",
       "      <td>item_2</td>\n",
       "      <td>18.447346</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2015-01-05</th>\n",
       "      <td>item_3</td>\n",
       "      <td>22.158983</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2015-01-06</th>\n",
       "      <td>item_1</td>\n",
       "      <td>17.254107</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2015-01-06</th>\n",
       "      <td>item_2</td>\n",
       "      <td>19.599428</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2015-01-06</th>\n",
       "      <td>item_3</td>\n",
       "      <td>22.687187</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "             level       pred\n",
       "2015-01-02  item_1  14.818313\n",
       "2015-01-02  item_2  17.954045\n",
       "2015-01-02  item_3  19.676498\n",
       "2015-01-03  item_1  14.961743\n",
       "2015-01-03  item_2  17.530592\n",
       "2015-01-03  item_3  19.207165\n",
       "2015-01-04  item_1  18.349711\n",
       "2015-01-04  item_2  17.792810\n",
       "2015-01-04  item_3  19.919855\n",
       "2015-01-05  item_1  18.639790\n",
       "2015-01-05  item_2  18.447346\n",
       "2015-01-05  item_3  22.158983\n",
       "2015-01-06  item_1  17.254107\n",
       "2015-01-06  item_2  19.599428\n",
       "2015-01-06  item_3  22.687187"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Predict using loaded forecaster\n",
    "# ==============================================================================\n",
    "forecaster_loaded.predict(steps=5, levels=None)  # Predict all levels"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "skforecast_py12",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.11"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": false,
   "sideBar": true,
   "skip_h1_title": true,
   "title_cell": "Tabla de contenidos",
   "title_sidebar": "Tabla de contenidos",
   "toc_cell": false,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "165px"
   },
   "toc_section_display": true,
   "toc_window_display": false
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "position": {
    "height": "144.391px",
    "left": "1478px",
    "right": "20px",
    "top": "126px",
    "width": "350px"
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
